package trinnes.oliver.unimaas.smartpentalath.control;

import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import trinnes.oliver.unimaas.smartpentalath.data.Board;
import trinnes.oliver.unimaas.smartpentalath.data.Cell;
import trinnes.oliver.unimaas.smartpentalath.data.Move;
import trinnes.oliver.unimaas.smartpentalath.data.Piece;
import trinnes.oliver.unimaas.smartpentalath.data.Validation;

/**
 *
 * @author Oliver Trinnes
 */
public class Game
{

  private Board board;
  private Player player1;
  private Player player2;
  private Player currentPlayer;
  private Player winner;
  private int ply;
  private boolean interruptedOrDraw;
  private int movesLeft;
  private Stack<Change> changes;
  private boolean swapAllowed;

  public Game(Board board, String player1Type, String player2Type)
  {
    this.board = board;

    if (player1Type.equals(HumanPlayer.class.getSimpleName()))
      player1 = new HumanPlayer(this, Piece.WHITE, "Human Player", 0);
    else if (player1Type.equals(SmartPlayer.class.getSimpleName()))
      player1 = new SmartPlayer(this, Piece.WHITE, "Smart Player", 0);
    else if (player1Type.equals(TestPlayer.class.getSimpleName()))
      player1 = new TestPlayer(this, Piece.WHITE, "Test Player", 0);

    if (player2Type.equals(HumanPlayer.class.getSimpleName()))
      player2 = new HumanPlayer(this, Piece.BLACK, "Human Player", 1);
    else if (player2Type.equals(SmartPlayer.class.getSimpleName()))
      player2 = new SmartPlayer(this, Piece.BLACK, "Smart Player", 1);
    else if (player2Type.equals(TestPlayer.class.getSimpleName()))
      player2 = new TestPlayer(this, Piece.BLACK, "Test Player", 1);

    movesLeft = -1;
    ply = 1;
    changes = new Stack<Change>();
    Change first = new Change();
    first.setLastMove(Move.NO_MOVE);
    changes.push(first);
  }

  public Game(Game original)
  {
    board = new Board(original.getBoard().getSize());
    for (int i = 0; i < original.getBoard().getCells().size(); i++)
    {
      board.putPiece(original.getBoard().getCells().get(i).getPiece(), i);
      board.getCells().get(i).setCenterDist(original.getBoard().getCells().get(i).getCenterDist());
    }

    Player player1Original = original.getPlayer1();
    if (player1Original instanceof HumanPlayer)
      this.player1 = new HumanPlayer(this, player1Original.getPiece(),
              player1Original.getName(), player1Original.getID());
    else if (player1Original instanceof RandomPlayer)
      this.player1 = new RandomPlayer(this, player1Original.getPiece(),
              player1Original.getName(), player1Original.getID());
    else if (player1Original instanceof SmartPlayer)
      this.player1 = new SmartPlayer(this, player1Original.getPiece(),
              player1Original.getName(), player1Original.getID());
    else if (player1Original instanceof TestPlayer)
      this.player1 = new TestPlayer(this, player1Original.getPiece(),
              player1Original.getName(), player1Original.getID());

    Player player2Original = original.getPlayer2();
    if (player2Original instanceof HumanPlayer)
      this.player2 = new HumanPlayer(this, player2Original.getPiece(),
              player2Original.getName(), player2Original.getID());
    else if (player2Original instanceof RandomPlayer)
      this.player2 = new RandomPlayer(this, player2Original.getPiece(),
              player2Original.getName(), player2Original.getID());
    else if (player2Original instanceof SmartPlayer)
      this.player2 = new SmartPlayer(this, player2Original.getPiece(),
              player2Original.getName(), player2Original.getID());
    else if (player2Original instanceof TestPlayer)
      this.player2 = new TestPlayer(this, player2Original.getPiece(),
              player2Original.getName(), player2Original.getID());

    Player currentPlayerOriginal = original.getCurrentPlayer();
    if (currentPlayerOriginal != null)
    {
      if (currentPlayerOriginal.equals(original.getPlayer1()))
        currentPlayer = player1;
      else
        currentPlayer = player2;
      changes = new Stack<Change>();
      Change first = new Change();
      first.setLastMove(Move.NO_MOVE);
      changes.push(first);
    }

    ply = original.getPly();
  }

  public void doAction(int move, Player player)
  {
    if (move == Move.NO_MOVE)
      return;

    if (move == Move.UNDO)
    {
      if (changes.empty())
        return;

      Change lastChange = changes.pop();

      if (lastChange.getLastMove() == Move.NO_MOVE)
        return;

      // Reset winner
      winner = null;

      // Undo swap
      if (lastChange.isSwapped())
      {
        Piece temp = getPlayer1().getPiece();
        getPlayer1().setPiece(getPlayer2().getPiece());
        getPlayer2().setPiece(temp);
        ply--;
        return;
      }

      // Remove set piece
      board.getCells().get(lastChange.getLastMove()).setPiece(Piece.EMPTY);

      // Reset captured pieces
      List<Cell> captured = lastChange.getCaptured();
      for (Cell cell : captured)
        cell.setPiece(lastChange.getCapturedPiece());

      // Decrement ply
      ply--;
    }
    else
    {
      Change newChange = new Change();
      if (move == Move.SWAP)
      {
        Piece temp = getPlayer1().getPiece();
        getPlayer1().setPiece(getPlayer2().getPiece());
        getPlayer2().setPiece(temp);
        newChange.setSwapped(true);
      }
      else
      {
        getBoard().putPiece(player.getPiece(), move);

        // Remove captured cells
        List<Cell> capturedCells = getBoard().getCapturedCells(move, player.
                getPiece());
        newChange.setCaptured(capturedCells);
        newChange.setCapturedPiece(getOpponent(player).getPiece());
        for (Cell cell : capturedCells)
          cell.setPiece(Piece.EMPTY);

        // Check for n in a row
        if (getBoard().isInARow(move, player.getPiece()))
          setWinner(player);
      }
      newChange.setLastMove(move);
      changes.push(newChange);
      ply++;
    }
  }

  public Stack<Change> getChanges()
  {
    return changes;
  }

  public Player getOpponent(Player player)
  {
    if (!player1.equals(player))
      return player1;
    else
      return player2;
  }

  public boolean isBusy()
  {
    return currentPlayer != null && !(currentPlayer instanceof HumanPlayer);
  }

  public boolean isInterruptedOrDraw()
  {
    return interruptedOrDraw;
  }

  public Board getBoard()
  {
    return board;
  }

  public int getPly()
  {
    return ply;
  }

  public int getMovesLeft()
  {
    return movesLeft;
  }

  public void setWinner(Player winner)
  {
    this.winner = winner;
  }

  public void setMovesLeft(int movesLeft)
  {
    this.movesLeft = movesLeft;
  }

  public Player getWinner()
  {
    return winner;
  }

  public void setPlayer2(Player player2)
  {
    this.player2 = player2;
  }

  public void setPlayer1(Player player1)
  {
    this.player1 = player1;
  }

  public void setCurrentPlayer(Player current)
  {
    this.currentPlayer = current;
  }

  public Player getPlayer2()
  {
    return player2;
  }

  public Player getPlayer1()
  {
    return player1;
  }

  public boolean isGameOver()
  {
    return winner != null || interruptedOrDraw;
  }

  public Player getCurrentPlayer()
  {
    return currentPlayer;
  }

  public boolean isSwapAllowed()
  {
    return swapAllowed;
  }

  public void setSwapAllowed(boolean swapAllowed)
  {
    this.swapAllowed = swapAllowed;
  }

  public Validation getValidation(int move, Piece piece)
  {
    // Swapping only allowed in ply 2, and if activated
    if (move < 0)
    {
      if (swapAllowed && move == Move.SWAP)
      {
        if (ply == 2)
          return Validation.VALID;
        else
          return Validation.NO_SWAP;
      }
      else if (move == Move.UNDO)
        return Validation.VALID;
      return Validation.INVALID;
    }

    // Cell must not be occupied
    if (board.isOccupied(move))
      return Validation.OCCUPIED;

    // No suicide  allowed
    if (board.isSuicide(move, piece))
      return Validation.SUICIDE;

    return Validation.VALID;
  }

  public String moveToString(int move)
  {
    if (move == Move.SWAP)
      return "Swap";
    if (move == Move.NO_MOVE)
      return "None";
    if (move == Move.UNDO)
      return "Undo";
    else
    {
      Point position = board.getPosition(move);
      char letter = (char) ('A' + position.x - 1);
      return letter + "" + position.y + "";
    }
  }

  public List<Integer> getPossibleMoves(Piece piece)
  {
    List<Integer> moves = new ArrayList<Integer>();

    for (int move = -1; move < board.getCells().size(); move++)
      if (getValidation(move, piece).equals(Validation.VALID))
        moves.add(move);
    return moves;
  }

  public int getPossibleMovesNumber(Piece piece)
  {
    int moves = 0;

    for (int move = -1; move < board.getCells().size(); move++)
      if (getValidation(move, piece).equals(Validation.VALID))
        moves++;
    return moves;
  }

  public void switchPlayer()
  {
    if (currentPlayer == null)
      return;
    if (currentPlayer.equals(player2))
      currentPlayer = player1;
    else
      currentPlayer = player2;
  }
}
